package com.example.lte.service;

import cn.dev33.satoken.stp.StpUtil;
import com.example.lte.entity.*;
import com.example.lte.po.LoginPO;
import com.example.lte.po.PermPO;
import com.example.lte.po.ReqDevice;
import com.example.lte.repo.ButtonRepo;
import com.example.lte.repo.MenuRepo;
import com.example.lte.repo.RoleUserRelRepo;
import com.example.lte.repo.UserRepo;
import com.example.lte.restful.APIException;
import com.example.lte.restful.ResultCodeEnum;
import com.example.lte.util.GeolocationUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @author lzh
 * @date 2020/10/13 - 14:30
 */
@Service
public class UserServiceImpl implements IUserService{

    @Autowired
    private LoginLogServiceImpl loginLogService;

    @Resource
    private UserRepo userRepo;
    @Resource
    private RoleUserRelRepo roleUserRelRepo;
    @Resource
    private MenuRepo menuRepo;
    @Resource
    private ButtonRepo buttonRepo;

    /**
     * 恢复默认密码
     *
     * @param userIds
     */
    @Override
    public void updateDefaultPassByIds(Long[] userIds) {
        userRepo.updateDefaultPassByIds(userIds);
    }

    /**
     * JpaSpecificationExecutor 多条件动态查询 类似于 mybatis xml里的if标签
     * @param detailParam UserEntity
     * @param pageable Pageable
     * @return  Page<UserEntity>
     */
    @Override
    public Page<UserEntity> findByCondition(UserEntity detailParam, Pageable pageable) {
        //     Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
        // 这里使用匿名Lamda实现 Specification
        // cb指sql条件构建器 javax.persistence.criteria.CriteriaBuilder
        // query指 sql查询器 javax.persistence.criteria.CriteriaQuery
        // root指   javax.persistence.criteria.Path
        //这里的匿名lambda写法是 jpa提供的，参考 org.springframework.data.jpa.domain.Specification
        return userRepo.findAll( (root, query, cb)->{
            List<Predicate> predicates = new ArrayList<>();
            //equal 示例
            if (!StringUtils.isEmpty(detailParam.getAccount())){
                predicates.add(cb.equal(root.get("account"),detailParam.getAccount()));
            }
            if (!StringUtils.isEmpty(detailParam.getId())){
                predicates.add(cb.equal(root.get("id"),detailParam.getId()));
            }

            //like 示例
            if (!StringUtils.isEmpty(detailParam.getName())){
                predicates.add(cb.like(root.get("name"),"%"+detailParam.getName()+"%"));
            }

            //between 示例
            /*if (detailParam.getMinAge()!=null && detailParam.getMaxAge()!=null) {
                Predicate agePredicate = cb.between(root.get("age"), detailParam.getMinAge(), detailParam.getMaxAge());
                predicates.add(agePredicate);
            }
            //greaterThan 大于等于示例
            if (detailParam.getMinAge()!=null){
                predicates.add(cb.greaterThan(root.get("age"),detailParam.getMinAge()));
            }*/


            return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
        }, pageable);
    }

    @Override
    public LoginPO doLogin(String account, String pwd, String clientIp, ReqDevice reqDevice ) {
        UserEntity u1 = userRepo.findByAccount(account);
        if (u1 != null){
            //SHA-1加密
            MessageDigest sha = null;
            try {
                sha = MessageDigest.getInstance("SHA");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            sha.update(pwd.getBytes());
            byte[] resultArr = sha.digest();
            String resultStr = byteArrayToHex(resultArr);

            if (!StringUtils.pathEquals(u1.getPassword(),resultStr)){
                throw new APIException(ResultCodeEnum.ACCOUNT_PASS_FAILED);
            }else {
                StpUtil.setLoginId(u1.getAccount());
                String tokenValue = StpUtil.getTokenInfo().get("tokenValue");

                String reqLocation = GeolocationUtil.getAddrByIp(clientIp);

                loginLogService.saveLoginLog(LoginLogEntity.builder()
                        .pubIp(clientIp)
                        .account(u1.getAccount())
                        .reqToken(tokenValue)
                        .reqLocation(reqLocation)
                        .browserName(reqDevice.getBrowserName())
                        .deviceSystem(reqDevice.getSystemName())
                        .deviceType(reqDevice.getDeviceType())
                        .build());

                return LoginPO.builder()
                        .account(account)
                        .token(tokenValue)
                        .userId(u1.getId())
                        .userName(u1.getName())
                        .build();

            }
        }else {
            throw new APIException(ResultCodeEnum.ACCOUNT_NOT_EXIST);
        }
    }

    public static String byteArrayToHex(byte[] byteArray) {
        // 首先初始化一个字符数组，用来存放每个16进制字符
        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

        // new一个字符数组，这个就是用来组成结果字符串的（解释一下：一个byte是八位二进制，也就是2位十六进制字符（2的8次方等于16的2次方））
        char[] resultCharArray = new char[byteArray.length * 2];

        // 遍历字节数组，通过位运算（位运算效率高），转换成字符放到字符数组中去
        int index = 0;
        for (byte b : byteArray) {
            resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];
            resultCharArray[index++] = hexDigits[b & 0xf];
        }

        // 字符数组组合成字符串返回
        return new String(resultCharArray);
    }

    @Override
    public PermPO getPremByUserId(Long userId) {

        List<RoleUserEntity> list1 = roleUserRelRepo.findByUserId(userId);
        List<Long> roleIds = list1.stream().map(RoleUserEntity::getRoleId).distinct().collect(Collectors.toList());

        List<MenuEntity> resultM = new ArrayList<>();
        List<ButtonEntity> resultB = new ArrayList<>();
        roleIds.forEach(p->{
            resultM.addAll(menuRepo.findMenuEntitiesByRoleId(p));
            resultB.addAll( buttonRepo.findButtonEntitiesByRoleId(p));
        });

        //hashSet是无序的
        Set<MenuEntity> setMenu = new HashSet<>(resultM);

        //利用TreeSet排序
        //TreeMap是有序的,这里按 MenuSequence 升序
        Set<MenuEntity> sortSetMenu = new TreeSet<MenuEntity>((o1, o2) -> o1.getMenuSequence().compareTo(o2.getMenuSequence()));
        sortSetMenu.addAll(setMenu);






        Set<ButtonEntity> setBtn = new HashSet<>(resultB);

        return PermPO.builder()
                .buttonEntitySet(setBtn)
                .menuEntitySet(sortSetMenu)
                .roleIds(roleIds)
                .userId(userId)
                .build();
    }

    @Override
    public UserEntity saveOne(UserEntity u) {
        return userRepo.save(u);
    }

    @Override
    public int updateNameAndSexById(String name, int sex, Long userId) {
        return userRepo.updateNameAndSexById(name, sex, userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delByIds(Long[] ids) {
        List<UserEntity> userEntities= userRepo.findAllById(Arrays.asList(ids));
        if (!CollectionUtils.isEmpty(userEntities)){
            //delete from user where id=? or id=?
            userRepo.deleteInBatch(userEntities);
        }
    }

    @Override
    public boolean isOnlineToday(Long[] userIds) {
        List<String> resList= loginLogService.getTargetOnlineToday(userIds);
        if (CollectionUtils.isEmpty(resList)){
            return false;
        }else {
            return true;
        }
    }
}
